
/**
 * AppleIIGo
 * Character Set Loader class
 * (C) 2008 by Marc S. Ressl (mressl@umich.edu)
 * Released under the GPL
 */

#include <stdio.h>
#include <string.h>

#include "CharacterSetLoader.h"

UINT8 charQuotation[] =			{0x14,0x14,0x14,0x00,0x00,0x00,0x00,0x00};
UINT8 charInvExclamation[] =	{0x08,0x00,0x08,0x08,0x08,0x08,0x08,0x00};
UINT8 charPound[] =				{0x38,0x44,0x04,0x0e,0x04,0x04,0x7a,0x00};
UINT8 charSection[] =			{0x3c,0x02,0x1c,0x22,0x1c,0x20,0x1e,0x00};
UINT8 charDiaeresis[] =			{0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
UINT8 charDegree[] =			{0x08,0x14,0x08,0x00,0x00,0x00,0x00,0x00};
UINT8 charInvQuestion[] =		{0x08,0x00,0x08,0x08,0x04,0x22,0x1c,0x00};
UINT8 charCapitalADiaeresis[] =	{0x22,0x08,0x14,0x22,0x3e,0x22,0x22,0x00};
UINT8 charCapitalARing[] =		{0x1c,0x22,0x1c,0x22,0x3e,0x22,0x22,0x00};
UINT8 charCapitalAE[] =			{0x3c,0x0a,0x0a,0x3e,0x0a,0x0a,0x3a,0x00};
UINT8 charCapitalNTilde[] =		{0x2c,0x1a,0x00,0x26,0x2a,0x32,0x22,0x00};
UINT8 charCapitalODiaeresis[] =	{0x22,0x1c,0x22,0x22,0x22,0x22,0x1c,0x00};
UINT8 charCapitalOStroke[] =	{0x20,0x1c,0x32,0x2a,0x2a,0x26,0x1c,0x02};
UINT8 charCapitalUDiaeresis[] =	{0x22,0x00,0x22,0x22,0x22,0x22,0x1c,0x00};
UINT8 charSharpS[] =			{0x1e,0x22,0x22,0x1e,0x22,0x22,0x1a,0x02};
UINT8 charAGrave[] =			{0x10,0x20,0x1c,0x20,0x3c,0x22,0x3c,0x00};
UINT8 charADiaeresis[] =		{0x00,0x22,0x1c,0x20,0x3c,0x22,0x3c,0x00};
UINT8 charARing[] =				{0x08,0x14,0x1c,0x20,0x3c,0x22,0x3c,0x00};
UINT8 charAE[] =				{0x00,0x00,0x16,0x28,0x3c,0x0a,0x3c,0x00};
UINT8 charCCedilla[] =			{0x00,0x00,0x3c,0x02,0x02,0x3c,0x10,0x08};
UINT8 charEGrave[] =			{0x10,0x20,0x1c,0x22,0x3e,0x02,0x3c,0x00};
UINT8 charEAcute[] =			{0x20,0x10,0x1c,0x22,0x3e,0x02,0x3c,0x00};
UINT8 charIGrave[] =			{0x04,0x08,0x00,0x0c,0x08,0x08,0x1c,0x00};
UINT8 charNTilde[] =			{0x2c,0x1a,0x00,0x1e,0x22,0x22,0x22,0x00};
UINT8 charOGrave[] =			{0x08,0x10,0x1c,0x22,0x22,0x22,0x1c,0x00};
UINT8 charODiaeresis[] =		{0x00,0x22,0x1c,0x22,0x22,0x22,0x1c,0x00};
UINT8 charOStroke[] =			{0x00,0x20,0x1c,0x32,0x2a,0x26,0x1c,0x02};
UINT8 charUGrave[] =			{0x04,0x08,0x22,0x22,0x22,0x32,0x2c,0x00};
UINT8 charUDiaeresis[] =		{0x00,0x22,0x00,0x22,0x22,0x32,0x2c,0x00};

UINT8 characterSetLocalizationMapChar[10] =
	{'#', '@',
		'[', '\\', ']', '`',
		'{', '|', '}', '~'};

UINT8 * characterSetLocalizationMap[CHARACTERSETLANGUAGE_END][10] = {
	// Default
	{0, 0,
		0, 0, 0, 0,
		0, 0, 0, 0},
	// UK
	{charPound, 0,
		0, 0, 0, 0,
		0, 0, 0, 0},
	// French
	{charPound, charAGrave,
		charDegree, charCCedilla, charSection, 0,
		charEAcute, charUGrave, charEGrave, charDiaeresis},
	// Danish
	{0, 0,
		charCapitalAE, charCapitalOStroke, charCapitalARing, 0,
		charAE, charOStroke, charARing, 0},
	// Spanish
	{charPound, charSection,
		charInvExclamation, charCapitalNTilde, charInvQuestion, 0,
		0, charNTilde, charCCedilla, 0},
	// Italian
	{charPound, charSection,
		0, charCCedilla, charEAcute, charUGrave,
		charAGrave, charOGrave, charEGrave, charIGrave},
	// German
	{0, charSection,
		charCapitalADiaeresis, charCapitalODiaeresis, charCapitalUDiaeresis, 0,
		charADiaeresis, charODiaeresis, charUDiaeresis, charSharpS},
	// Swedish
	{0, 0,
		charCapitalADiaeresis, charCapitalODiaeresis, charCapitalARing, 0,
		charADiaeresis, charODiaeresis, charARing, 0},
	};

CharacterSetLoader::CharacterSetLoader(UINT32 videoType,
									   Resources * resources) {
	isIIeEnhanced = (videoType != VIDEOTYPE_II) && (videoType != VIDEOTYPE_IIE);
	this->resources = resources;
	
	initLanguage();
	initCharacterSet();
}

void CharacterSetLoader::initLanguage() {
	languageIndex = CHARACTERSETLANGUAGE_DEFAULT;
}

void CharacterSetLoader::initCharacterSetMap() {
	for (UINT32 index = 0; index < CHARACTERSETMAP_END; index++) {
		UINT32 expandedIndex = index & 0xff;
		bool isAlt = getBit(index, CHARACTERSETMAP_ALT);
		bool isFlash = getBit(index, CHARACTERSETMAP_FLASH);
		
		switch (characterSetType) {
			case CHARACTERSETTYPE_II:
				if (isFlash)
					expandedIndex |= 0x100;
				break;
			case CHARACTERSETTYPE_IIJPLUS:
				if (isFlash && getBit(expandedIndex, (1 << 6)))
					expandedIndex |= 0x100;
				setBit(expandedIndex, (1 << 6),
					   getBit(expandedIndex, (1 << 7)));
				setBit(expandedIndex, (1 << 7), isAlt);
				break;
			case CHARACTERSETTYPE_IIE:
				if (!isAlt && ((expandedIndex & 0xc0) == 0x40)) {
					expandedIndex &= 0x3f;
					if (isFlash)
						expandedIndex |= 0x80;
				}
				break;
		}
		
		characterSetMap[index] =
			&expandedCharacterSet[expandedIndex * APPLEII_CHARHEIGHT];
	}
}

void CharacterSetLoader::initCharacterSet() {
	memset(characterSet, 0, CHARACTERSET_BYTENUM),
	characterSetType = CHARACTERSETTYPE_II;
	
	updateCharacterSet();
	initCharacterSetMap();
}

bool CharacterSetLoader::loadCharacterSet(UINT32 characterSetType,
										  char * characterSetRomImage) {
	UINT8 fileCharacterSet[CHARACTERSET_BYTENUM];
	
	if (resources->getResourceByteNum(characterSetRomImage) <
		CHARACTERSET_BYTENUM)
		return false;
	
	if (resources->readResource(fileCharacterSet, CHARACTERSET_BYTENUM,
								characterSetRomImage) != 
		CHARACTERSET_BYTENUM)
		return false;
	
	this->characterSetType = characterSetType;
	convertCharacterSet(characterSet, fileCharacterSet, characterSetType);
	updateCharacterSet();
	initCharacterSetMap();
	
	return true;
}

void CharacterSetLoader::setLanguage(UINT32 languageIndex) {
	if (languageIndex < CHARACTERSETLANGUAGE_END) {
		this->languageIndex = languageIndex;
		updateCharacterSet();
	}
}

UINT8 ** CharacterSetLoader::getCharacterSetMap() {
	return characterSetMap;
}

UINT32 CharacterSetLoader::reverseBits(UINT32 value, UINT32 bitNum) {
	UINT32 bitReversedValue = 0;
	
	while (bitNum--) {
		bitReversedValue <<= 1;
		bitReversedValue |= value & 1;
		value >>= 1;
	}
	
	return bitReversedValue;
}

void CharacterSetLoader::convertCharacterSet(UINT8 * destinationCharacterSet,
											 UINT8 * sourceCharacterSet,
											 UINT32 characterSetType) {
	switch (characterSetType) {
		case CHARACTERSETTYPE_II:
		case CHARACTERSETTYPE_IIJPLUS:
			for (UINT32 index = 0; index < CHARACTERSET_BYTENUM; index++)
				destinationCharacterSet[index] =
					reverseBits(sourceCharacterSet[index], 7) |
					(sourceCharacterSet[index] & 0x80);
			break;
		case CHARACTERSETTYPE_IIE:
			for (UINT32 index = 0; index < CHARACTERSET_BYTENUM; index++)
				destinationCharacterSet[index] =
					(~sourceCharacterSet[index]) & 0x7f;
			break;
	}
}

void CharacterSetLoader::copyCharacterSetChar(UINT8 * localizedCharacterSet,
											  UINT32 characterSetIndex,
											  UINT8 * characterSetChar,
											  bool isInverted) {
	for (UINT32 index = 0; index < 8; index++)
		localizedCharacterSet[characterSetIndex * APPLEII_CHARHEIGHT + index] =
			isInverted ? ((~characterSetChar[index]) & 0x7f) :
			(characterSetChar[index]);
}

void CharacterSetLoader::localizeCharacter(UINT8 * localizedCharacterSet,
										   UINT32 asciiIndex,
										   UINT8 * characterSetChar) {
	UINT32 index = (asciiIndex & 0x1f);
	
	switch (asciiIndex & 0x60) {
		case 0x20:
			copyCharacterSetChar(localizedCharacterSet, 0x20 + index,
								 characterSetChar, true);
			copyCharacterSetChar(localizedCharacterSet, 0xa0 + index,
								 characterSetChar, false);
			break;
		case 0x40:
			asciiIndex &= 0x20;
			copyCharacterSetChar(localizedCharacterSet, 0x00 + index,
								 characterSetChar, true);
			if (!isIIeEnhanced)
				copyCharacterSetChar(localizedCharacterSet, 0x40 + index,
									 characterSetChar, true);
			copyCharacterSetChar(localizedCharacterSet, 0x80 + index,
								 characterSetChar, false);
			copyCharacterSetChar(localizedCharacterSet, 0xc0 + index,
								 characterSetChar, false);
			break;
		case 0x60:
			copyCharacterSetChar(localizedCharacterSet, 0x60 + index,
								 characterSetChar, true);
			copyCharacterSetChar(localizedCharacterSet, 0xe0 + index,
								 characterSetChar, false);
			break;
	}
}

void CharacterSetLoader::localizeCharacterSet(UINT8 * localizedCharacterSet,
											  UINT8 * characterSet,
											  UINT32 characterSetType,
											  UINT32 languageIndex) {
	memcpy(localizedCharacterSet, characterSet, CHARACTERSET_BYTENUM);
	
	if (characterSetType != CHARACTERSETTYPE_IIE)
		return;
	
	for (UINT32 index = 0; index < 10; index++) {
		if (characterSetLocalizationMap[languageIndex][index])
			localizeCharacter(localizedCharacterSet,
							  characterSetLocalizationMapChar[index],
							  characterSetLocalizationMap[languageIndex]
							  [index]);
	}
}

void CharacterSetLoader::expandCharacterSet(UINT8 * expandedCharacterSet,
											UINT8 * characterSet) {
	for (UINT32 index = 0; index < CHARACTERSET_BYTENUM; index++) {
		UINT32 characterIndex = (index / APPLEII_CHARHEIGHT);
		UINT32 normalCharacter = characterSet[index] & 0x7f;
		UINT32 invertedCharacter = (~characterSet[index]) & 0x7f;
		
		switch (characterSetType) {
			case CHARACTERSETTYPE_II:
				if (characterIndex & 0x80) {
					expandedCharacterSet[index] = normalCharacter;
					expandedCharacterSet[index + CHARACTERSET_BYTENUM] =
						normalCharacter;
				} else {
					expandedCharacterSet[index] = invertedCharacter;
					expandedCharacterSet[index + CHARACTERSET_BYTENUM] =
						(characterSet[index] & 0x80) ? normalCharacter :
						invertedCharacter;
				}
				break;
			case CHARACTERSETTYPE_IIJPLUS:
				if (characterIndex & 0x40) {
					expandedCharacterSet[index] = normalCharacter;
					expandedCharacterSet[index + CHARACTERSET_BYTENUM] =
						normalCharacter;
				} else {
					expandedCharacterSet[index] = invertedCharacter;
					expandedCharacterSet[index + CHARACTERSET_BYTENUM] =
						normalCharacter;
				}
				break;
			case CHARACTERSETTYPE_IIE:
				expandedCharacterSet[index] = normalCharacter;
				break;
		}
	}
}

void CharacterSetLoader::updateCharacterSet() {
	UINT8 localizedCharacterSet[CHARACTERSET_BYTENUM];
	
	localizeCharacterSet(localizedCharacterSet, characterSet, characterSetType,
						 languageIndex);
	expandCharacterSet(expandedCharacterSet, localizedCharacterSet);
}
